package org.litespring.aop.framework;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.litespring.aop.Advice;
import org.litespring.util.Assert;
import org.springframework.cglib.core.SpringNamingPolicy;
import org.springframework.cglib.proxy.*;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;

/**
 * @author ChangLiang
 * @date 2020/8/25
 */
public class CglibProxyFactory implements AopProxyFactory {

    // enhancer.setCallbacks(new Callback[]{new BookDaoProxy(), NoOp.INSTANCE}); 数组中的索引
    // Constants for CGLIB callback array indices
    private static final int AOP_PROXY = 0;
    // 下面的内容在这里没有实现 只保留了上面的一个
    private static final int INVOKE_TARGET = 1;
    private static final int NO_OVERRIDE = 2;
    private static final int DISPATCH_TARGET = 3;
    private static final int DISPATCH_ADVISED = 4;
    private static final int INVOKE_EQUALS = 5;
    private static final int INVOKE_HASHCODE = 6;

    protected static final Log logger = LogFactory.getLog(CglibProxyFactory.class);

    protected final AopConfig config;

    private Object[] constructorArgs;

    private Class<?>[] constructorArgTypes;

    public CglibProxyFactory(AopConfig config) {
        Assert.notNull(config, "AdvisedSupport must not be null");
        this.config = config;
    }

    @Override
    public Object getProxy() {
        return getProxy(null);
    }

    @Override
    public Object getProxy(ClassLoader classLoader) {
        try {
            Class<?> rootClass = this.config.getTargetClass();

            // Configure CGLIB Enhancer...
            Enhancer enhancer = new Enhancer();
            if (classLoader != null) {
                enhancer.setClassLoader(classLoader);
            }
            enhancer.setSuperclass(rootClass);

            // "BySpringCGLIB"
            enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
            enhancer.setInterceptDuringConstruction(false);

            Callback[] callbacks = getCallbacks(rootClass);
            Class<?>[] types = new Class<?>[callbacks.length];
            for (int x = 0; x < types.length; x++) {
                types[x] = callbacks[x].getClass();
            }

            enhancer.setCallbackFilter(new ProxyCallbackFilter(this.config));
            enhancer.setCallbackTypes(types);
            enhancer.setCallbacks(callbacks);

            // Generate the proxy class and create a proxy instance.
            Object proxy = enhancer.create();

            return proxy;

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private Callback[] getCallbacks(Class<?> rootClass) throws Exception{
        Callback aopInterceptor = new DynamicAdvisedInterceptor(this.config);
        Callback[] callbacks = new Callback[]{aopInterceptor};
        return callbacks;
    }

    private static class DynamicAdvisedInterceptor implements MethodInterceptor, Serializable{

        private final AopConfig config;

        public DynamicAdvisedInterceptor(AopConfig advised) {
            this.config = advised;
        }

        @Override
        public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
            Object target = this.config.getTargetObject();

            // 拿到匹配这个方法的Advice List
            List<Advice> chain = this.config.getAdvices(method);
            Object retVal;

            // Check whether we only have one InvokerInterceptor: that is,
            // no real advice, but just reflective invocation of the target.
            if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
                // We can skip creating a MethodInvocation: just invoke the target directly.
                // Note that the final invoker must be an InvokerInterceptor, so we know
                // it does nothing but a reflective operation on the target, and no hot
                // swapping or fancy proxying.
                retVal = methodProxy.invoke(target, args);
            } else {
                List<org.aopalliance.intercept.MethodInterceptor> interceptors = new ArrayList<>();
                interceptors.addAll(chain);
                // We need to create a method invocation...
                retVal = new ReflectiveMethodInvocation(target,method,args,interceptors).proceed();
            }
            return retVal;
        }
    }

    private static class ProxyCallbackFilter implements CallbackFilter{

        private final AopConfig config;

        public ProxyCallbackFilter(AopConfig advised) {
            this.config = advised;
        }

        @Override
        public int accept(Method method) {
            return AOP_PROXY;
        }
    }
}
